// 基础类型推论
// TypeScript里,在有些没有明确指出类型的地方,
// 类型推论会帮助提供类型
let x = 3 ;
x = "S";
// 此处虽然没有直接声明x的变量类型
// 但是因为x被赋值为数字3
// 那么x变量的数组类型就会被自动限制为数字
// 这种推断发生在初始化变量和成员,设置默认参数值和决定函数返回值时

// 最佳通用类型
// 但需要从几个表达式中推断类型时候,
// 会使用这些表达式的类型来推断出一个最合适的通用类型
let y = [0,1,null];
// 此处的类型推论分两步
// 第一步,变量x的值必须是数组
// 第二步,该数组的每个项目必须的得是数字
// 于是x得类型就是x:number[]

// 报错,不允许玩该数组内添加字符串
y.push("1");
// 为了推断x得类型
// 我们必须考虑所有元素得类型
// 这里有两种选择:number和null
// 计算通用耐心算法会开率所有的候选类型
// 并给出一个兼容是所有候选类型的类型

// 由于追踪的通用类型取自候选类型
// 有些时候候选类型共享相同的通用类型,
// 但是却没有一个类型能作为所有候选类型的类型
class Rhino{}
class Elephant{}
class Snake{}
interface Animal{}
let zoo = [new Rhino(),new Elephant(),new Snake()];
// 这里我们想让zoo被推断为Animal[]类型,
// 但是这个数组里没有对象是Animal类型的
// 因此不能推断出这个结果
// 为了更正,当候选类型不能使用的时候我们需要明确的指出类型
let zoo2:Animal[] = [new Rhino(),new Elephant(),new Snake()];
// 如果没有找到最佳推断的结果为联合数组类型
// (Rhino,Elephant,Snake)[]

// 上下文类型推论(上下文推论)
// TypeScript类型推论也可能按照相反的方向进行
// 这叫做"按上下文归类"
// 按上下文归类会发生在表达式的类型与所处的位置相关时

// Typescript 类型检查器使用window.onmousedown函数的类型
// 来推断右边函数表达式的类型
// 因此,就能推断出mouseEvent参数的类型了
window.onmousedown = function(mouseEvent){
    console.log(mouseEvent.button);
}

// 如果上下文类型表达式包好了明确的类型信息,上下文类型被忽略
window.onmousedown = function(mouseEvent:any){
    console.log(mouseEvent.button);
}

// 上下文归类会在很多情况下使用到
    // 通常包含函数的参数
    // 赋值表达式的右边
    // 类型断言
    // 对象成员和数组字面量和返回值语句
    // 上下文类型也会作为最佳通用类型的候选类型
function createZoo():Animal[]{
    return [new Rhino(),new Elephant(),new Snake()];
}
// 这个例子里,最佳通用类型有4个候选者:Animal, Rhino, Elephant, Snack 
// 当然Animal会被作通用类型

// 基础类型推论
// TypeScript里，在有些没有明确指出类型的地方类型推论会帮助提供类型
let x = 3;
// 此处虽然没有直接声明x变量的值的类型，但是因为x被赋值为数字3
// 那么x变量的数据类型就会被自动限制为数字
x = 's';//报错，x变量只能存储数字类型
// 这种推断发生在初始化变量和成员，设置默认参数值和决定函数返回值时

// TypeScript的类型兼容模型
// 目前在所有的编程语言中，对于类型兼容的实现除了结构类型外，
// 还有一个叫做名义类型，结构类型和名义类型之间区别如下：
    // 1.在基于名义类型的类型系统中，数据类型的兼容性或等价性2是通过明确的声明和类型的名称来决定的
    // 2.在基于结构性的类型系统中，数据类型的兼容性或等价性是基于类型的组成结构，且不要求明确的声明
// TypeScript里的类型兼容性是基于结构子类型的，使用其成员来描述类型的方式
interface Named{
    name:string;
}
class Person{
    name:string;
}
class Mother{
    age:number;
}
let p:Named;
p = new Person();
// ok这个符合格式，因为Preson的实力1里面有一个名为name的属性，这完全符合接口要求


// TypeScript的结构化类型系统
// TypeScript的结构性子类型是根据JavaScript代码的典型写法来设计
// 因为JavaScript里广泛地使用匿名对象，例如函数表达式和对象字面量
// 所以使用结构类型系统来描述这些类型比使用名义类型系统更好
// TypeScript结构化类型系统的基本规则是，如果x要兼容y，那么y至少具有与x相同的属性

interface Named{
    name:string;   
}
let x:Named;
let y = {name:'Alice',location:'Seattle'};
x = y;
// 这里要检查y能否复制给x，编译器检查中的每个属性，看是否能在y中也找到对应属性
// 在这个例子中,y必须包含名字是name的string类型成员,y满足条件,因此复制正确

function greet(n:Named){
    console.log('Hello,' + n.name);
}
greet(y);
// 注意,y有个额外的location属性,但这不会引发错误,只有目标类型(这里是Named)的成员会被一一检查是否兼容
// 这个比较过程说是递归进行的,检查每个成员以及子成员

// 类型系统强制原函数的返回值类型必须是目标函数返回函数的子类型

// 可选参数以及剩余参数
// 比较函数兼容性的时候,可选参数与必须参数是可呼唤的,源类型上有额外的可选参数不是错误,
// 目标类型的可选参数在源类型里没有对应的参数也不是错误
function invokeLater(args:any[],callback:(...args:any[])=>void){
}
invokeLater([1,2],(x,y) => console.log(x+','+y));
invokeLater([1,2],(x?,y?) => console.log(x+','+y));
// 当一个函数有剩余参数时,它被当作无限个可选参数,这对于类型系统来说不是稳定的,
// 但从运行时的角度来看,可选参数一般来说是不强制的,因为对于大多数函数来说相当于传递了一些undefined


// 枚举类型的比较
// 枚举类型与数字类型兼容,并且数字类型与枚举类型兼容,不同枚举类型之间是不兼容的
enum Status {Ready,waiting};
enum Color  {Red,blue,Green};
let s = Status.Ready;
s = 3; 
s = Color.Green


// 类的类型比较
// 类与对象字面量和接口差不多, 但有一点不同:类有静态部分和实力部分的类型
// 比较两个类类型的对象时,只有实例成员会被比较.静态长远和构造函数不在比较的范围内
class Animal4{
    feet:number;
    static sayMyName(){
        console.log("hello");
    }
    constructor(name:string,numFeet:number){}
}
class Size{
    feet:number;
    constructor(numFeet:number){}
}
let a:Animal;
let s2:Size;
a = s2;
s2 = a;
// 类的私有成员和受保护成员会影响兼容性
// 当检查类实力的兼容时,如果目标类型包含一个私有成员,
// 那么源类型必须包含来自同一个类的私有成员
// 同样的,这条规则也使用与包含受保护成员实力的类型检查,
// 这允许字类赋值给父类,但是不能赋值给其他有同样类型的类型

// 泛型的类型兼容
// 因为TypeScipt是结构性的类型系统，类型参数只影响使用其作为类型的一部分的结果类型

interface Empyt<T> {}
let xx:Empyt<number>;
let yy:Empyt<string>;
xx = yy;
// 上面的代码里，x和y是兼容的，因为他们的结构使用类型参数时并没有什么不同

interface NotEmpty<T>{
    data:T
}
let xxx:NotEmpty<number>;
let yyy:NotEmpty<string>;
xxx = yyy;
// 在这里，泛型类型在使用时就好比不是一个泛型类型

// 对于没指定泛型类型的泛型参数时，会把所有泛型参数当成any比较 然后用结果类型进行比较
let identity = function<T>(x:T):T{}
let reverse  = function<U>(y:U):U{}
identity = resizeBy;

// 子类型与赋值的理论辨析

// 目前为止，我们使用了“兼容性”，它在语言规范里没有定义，在TypeScript里，有两种兼容性：子类型和赋值
// 他们的不同点在于赋值与扩展了子类型兼容性，增加了一些规则，允许和any来回赋值，
// 以及enum和对应数字值之间的来回赋值
// 语言里的不同地方分别使用了他们之中的机制
// 实际上, 类型的兼容是由赋值兼容性来控制的，即使在implements和extends语句也不例外
